#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <unistd.h>
#include <math.h>
#include "pt100_test.h"
#include "pcie_mem.h"

enum pt100_bar0_offset_t {
    PT100_CMD_BAR0_OFFSET = 0x08,          /* PT100 命令寄存器偏移 */
    PT100_ID_DATA_BAR0_OFFSET = 0x38,      /* PT100 ID 数据寄存器偏移 */
    PT100_SET_MODE_BAR0_OFFSET = 0x48,     /* PT100 设置模式寄存器偏移 */
};

#define PT100_START_CMD        0x1111      /* 启动 PT100 命令 */
#define PT100_STOP_CMD         0x2222      /* 停止 PT100 命令 */
#define PT100_CNOFIG_CMD       0x3333      /* 配置 PT100 命令 */

#define  PT100_DATA_SHIFT       0           /* PT100 数据左移位数 */
#define  PT100_ID_SHIFT         24          /* PT100 ID 左移位数 */

#define ADC_10V_RANGE           0x00
#define ADC_2V5_RANGE           0x01
#define ADC_5V_RANGE            0X02

#define ADC_10V_RANGE_LSB       305         /* 305uV */
#define ADC_2V5_RANGE_LSB       76          /* 76uV */
#define ADC_5V_RANGE_LSB        152         /* 152uV */

#define PT100_TSAS_SHIFT       0           /* PT100 TSAS 左移位数 */
#define PT100_MODE_SHIFT       19          /* PT100 模式左移位数 */

#define PT100_DEFAULT_MODE     0x03
#define PT100_DEFAULT_TSAS     10           /* us */

struct pt100_info_t {
    uint8_t channel;                        /* 通道号 */
    uint8_t channel_min;                    /* 最小通道号 */
    uint8_t channel_max;                    /* 最大通道号 */
    double value;                           /* PT100 输入值 */
};

/* PT100温度-电阻关系多项式系数 (根据IEC 60751标准) */
/* 温度范围：-200°C 至 0°C: R(t) = R0[1 + At + Bt² + C(t-100)t³] */
/* 温度范围：0°C 至 850°C: R(t) = R0(1 + At + Bt²) */

#define R0 100.0  /* PT100在0°C时的电阻值 */

/* 系数定义 (IEC 60751标准) */
#define A 3.9083e-3
#define B -5.775e-7
#define C -4.183e-12

/**
 * @brief 使用牛顿迭代法计算PT100电阻对应的温度
 * @param resistance 测得的电阻值(Ω)
 * @param initial_guess 初始温度猜测值(°C)
 * @param max_iterations 最大迭代次数
 * @param tolerance 收敛容差
 * @return 计算得到的温度值(°C)，如果计算失败返回NAN
 */
static double pt100_resistance_to_temp(double resistance, double initial_guess, 
                               int max_iterations, double tolerance) {
    double t = initial_guess;   /* 初始温度猜测 */
    int iteration = 0;
    
    while (iteration < max_iterations) {
        double rt, drdt;
        
        /* 根据温度范围选择不同的多项式 */
        if (t < 0) {
            /* -200°C 至 0°C 范围 */
            rt = R0 * (1 + A * t + B * t * t + C * (t - 100) * t * t * t);
            drdt = R0 * (A + 2 * B * t + C * (4 * t * t * t - 300 * t * t));
        } else {
            /* 0°C 至 850°C 范围 */
            rt = R0 * (1 + A * t + B * t * t);
            drdt = R0 * (A + 2 * B * t);
        }
        
        /* 计算函数值和导数 */
        double f = rt - resistance;
        double f_prime = drdt;
        
        /* 检查导数是否接近零，避免除零错误 */
        if (fabs(f_prime) < 1e-10) {
            return NAN;  /* 返回非数值表示计算失败 */
        }
        
        /* 牛顿迭代公式: t_new = t_old - f(t)/f'(t) */
        double t_new = t - f / f_prime;
        
        /* 检查是否收敛 */
        if (fabs(t_new - t) < tolerance) {
            return t_new;
        }
        
        t = t_new;
        iteration++;
    }
    
    /* 如果达到最大迭代次数仍未收敛 */
    return NAN;
}

/**
 * @brief 简化接口函数，使用默认参数
 * @param resistance 测得的电阻值(Ω)
 * @return 计算得到的温度值(°C)
 */
static double pt100_calculate_temp(double resistance) {
    /* 使用默认参数：初始猜测为0°C，最大迭代100次，容差0.001°C */
    return pt100_resistance_to_temp(resistance, 0.0, 100, 0.01);
}


static void set_ai_mode(struct pcie_mem_ctx *ctx, uint32_t value)
{
    volatile uint32_t *addr;

    if (!ctx->map_base) {
        printf("错误: 内存未映射!\n");
        return;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    *(volatile uint32_t *)((uintptr_t)addr + PT100_SET_MODE_BAR0_OFFSET) = value;

    printf("写入: reg=0x%04X, val=0x%08X\n",
            PT100_SET_MODE_BAR0_OFFSET, value);
}

static void set_ai_cmd(struct pcie_mem_ctx *ctx, uint32_t value)
{
    volatile uint32_t *addr;

    if (!ctx->map_base) {
        printf("错误: 内存未映射!\n");
        return;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    *(volatile uint32_t *)((uintptr_t)addr + PT100_CMD_BAR0_OFFSET) = value;

    printf("写入: reg=0x%04X, val=0x%08X\n",
            PT100_CMD_BAR0_OFFSET, value);
}

static uint32_t get_single_pt100(struct pcie_mem_ctx *ctx, uint8_t channel)
{
    volatile uint32_t *addr;
    uint32_t value;

    if (!ctx->map_base) {
        printf("错误: 内存未映射!\n");
        return 0;
    }

    addr = (volatile uint32_t *)((uintptr_t)ctx->map_base);
    *(volatile uint32_t *)((uintptr_t)addr +  PT100_ID_DATA_BAR0_OFFSET) = (channel <<  PT100_ID_SHIFT);
    /* 注意：这里需要将字节偏移转换为 uint32_t 偏移 */
    value = *(addr + ( PT100_ID_DATA_BAR0_OFFSET / sizeof(uint32_t)));

    printf("读取: reg=0x%04X, val=0x%08X\n",
             PT100_ID_DATA_BAR0_OFFSET, value);

    return (value & 0xFFFFFF);
}

uint32_t get_single_pt100_test(struct pcie_mem_ctx *ctx, struct pt100_info_t pt100_info)
{
    uint32_t reg_val = 0;

    if ((pt100_info.channel >= pt100_info.channel_min) && (pt100_info.channel <= pt100_info.channel_max)) {
        reg_val = get_single_pt100(ctx, pt100_info.channel);
        pt100_info.value = reg_val * ADC_10V_RANGE_LSB / 1000000.0f;
        printf("读取通道 %d 的 PT100 输入值为 %.6lfV\n", pt100_info.channel, pt100_info.value);
        pt100_info.value *= 100.0f;
        printf("读取通道 %d 的 PT100 输入值为 %.6lfΩ\n", pt100_info.channel, pt100_info.value);
        pt100_info.value = pt100_calculate_temp(pt100_info.value);
        printf("读取通道 %d 的 PT100 输入值为 %.6lf℃\n", pt100_info.channel, pt100_info.value);
    } else {
        printf("参数错误: %d ≤ channel=%d ≤ %d\n",
               pt100_info.channel_min, pt100_info.channel, pt100_info.channel_max);
        reg_val = 0;
    }

    return reg_val;
}

static int usage(int argc, char *argv[])
{
    if ((argc < 2) || (argc > 3)) {
        printf("用法: %s %s channel\n", argv[0], argv[1]);
        printf("示例: %s %s 0\n", argv[0], argv[1]);
        printf("用法: %s %s n", argv[0], argv[1]);
        printf("示例: %s %s \n", argv[0], argv[1]);
        return EXIT_FAILURE;
    } else {
        return EXIT_SUCCESS;
    }
}

int pt100_test(int argc, char *argv[])
{
    int ret = 0;
    uint8_t i = 0;
    const char *device_address = PCIE_PT100_DEVICE_ADDRESS;
    uint32_t base_addr = PCIE_PT100_BASE_ADDR_BAR0;
    size_t map_size = PCIE_PT100_BAR0_MAP_SIZE;
    struct pcie_mem_ctx ctx = {
        .fd = -1,
        .map_base = NULL,
        .phys_base = 0,
        .map_size = 0
    };
    struct pt100_info_t pt100_info = {
        .channel = 8,
        .channel_min = 8,
        .channel_max = 15,
        .value = 0,
    };

    if (EXIT_SUCCESS != usage(argc, argv)) {
        return EXIT_FAILURE;
    }

    if (argc == 3) {
        pt100_info.channel = atoi(argv[2]);
    } else if (argc == 2) {
        pt100_info.channel = pt100_info.channel_max + 1;
    }

    printf("正在获取通道 %d 的 PT100...\n",
            pt100_info.channel);

    if (true == is_pcie_device_enabled(device_address)) {
        printf("设备已启用\n");
    } else {
        printf("设备未启用\n");
        if (enable_pcie_device(device_address) == 0) {
            printf("设备启用成功\n");
        } else {
            printf("设备启用失败!\n");
            return EXIT_FAILURE;
        }
    }

    ret = init_pcie_mem(&ctx, base_addr, map_size);
    if (ret < 0) {
        printf("初始化内存映射失败!\n");
        return EXIT_FAILURE;
    }

    set_ai_mode(&ctx, (PT100_DEFAULT_MODE << PT100_MODE_SHIFT | PT100_DEFAULT_TSAS << PT100_TSAS_SHIFT));
    set_ai_cmd(&ctx, PT100_CNOFIG_CMD);
    usleep(100000);
    set_ai_cmd(&ctx, PT100_START_CMD);

    if (pt100_info.channel == (pt100_info.channel_max + 1)) {
        for (i = pt100_info.channel_min; i <= pt100_info.channel_max; i++) {
            pt100_info.channel = i;
            get_single_pt100_test(&ctx, pt100_info);
        }
    } else {
        get_single_pt100_test(&ctx, pt100_info);
    }

    cleanup_pcie_mem(&ctx);

    printf("通道 %d 的测试已完成\n", pt100_info.channel);

    return EXIT_SUCCESS;
}
